// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <hwreg/bitfields.h>
#include <lib/mmio/mmio.h>
#include <soc/mt8167/mt8167-hw.h>
#include <zircon/types.h>

namespace {
// There are 2 sets of GPIO Pull settings register banks, those under GPIO and those under IOCFG.
// Those under the GPIO have a consistent numbering mapping such that the register offsets
// can be calculated based on the GPIO number.  The GPIOs that fall into IOCFG are marked as '0'
// in kGpioPullInGpioRegs and return false in GpioPullEnReg/GpioPullSelReg methods to indicate that
// they are not supported in the GPIO registers so we then try IoConfigReg methods. Note that the
// last 3 GPIO numbers in the array don't fall under GPIO or IOCFG (as any other number past 127).
static constexpr bool kGpioPullInGpioRegs[][16] = {
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0},  // 0 (first GPIO in this line).
    {0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1},  // 16.
    {1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1},  // 32.
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},  // 48.
    {1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1},  // 64.
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},  // 80.
    {1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0},  // 96.
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0},  // 112 (first is 112, last is 127).
};

}  // namespace

namespace gpio {

enum PullAmount {
  kNoPull,
  kPull10K,
  kPull50K,
  kPull10K50K,
  kPull75K,
  kPull2K,
  kPull75K2K,
  kPull200K,
  kPull75K200K,
};

// GPIO MODE defines PINMUX for this device
class GpioModeReg : public hwreg::RegisterBase<GpioModeReg, uint16_t> {
 public:
  static constexpr uint16_t kModeGpio = 0;  // GPIO mode is always 0
  static constexpr uint64_t kModeMax = 8;   // 3 bits per mode

  static uint16_t GetMode(ddk::MmioBuffer* mmio, size_t idx) {
    return Read(mmio, idx).GetMode(idx % kItemsPerReg);
  }

  static void SetMode(ddk::MmioBuffer* mmio, size_t idx, uint16_t value) {
    Read(mmio, idx).SetMode(idx % kItemsPerReg, value).WriteTo(mmio);
  }

 private:
  static constexpr size_t kItemsPerReg = 5;

  // Registers are 16 bits, separated by 0x10 bytes, with kItemsPerReg values per register.
  static uint32_t Idx2Offset(size_t idx) {
    return static_cast<uint32_t>(0x300 + (idx / kItemsPerReg) * 0x10);
  }

  uint16_t GetMode(size_t offset) const {
    switch (offset) {
      case 0:
        return mode0();
      case 1:
        return mode1();
      case 2:
        return mode2();
      case 3:
        return mode3();
    }
    return mode4();
  }

  GpioModeReg& SetMode(size_t offset, uint16_t value) {
    switch (offset) {
      case 0:
        return set_mode0(value);
      case 1:
        return set_mode1(value);
      case 2:
        return set_mode2(value);
      case 3:
        return set_mode3(value);
    }
    return set_mode4(value);
  }

  static GpioModeReg Read(ddk::MmioBuffer* mmio, size_t idx) {
    return hwreg::RegisterAddr<GpioModeReg>(Idx2Offset(idx)).ReadFrom(mmio);
  }

  DEF_FIELD(14, 12, mode4);
  DEF_FIELD(11, 9, mode3);
  DEF_FIELD(8, 6, mode2);
  DEF_FIELD(5, 3, mode1);
  DEF_FIELD(2, 0, mode0);
};

class GpioBitFieldView : public ddk::MmioView {
  // Registers are 16 bits and separated by 0x10 bytes.
  uint32_t Idx2Offset(size_t idx) const { return static_cast<uint32_t>((idx / 16) * 0x10); }

 public:
  explicit GpioBitFieldView(mmio_buffer_t& mmio, zx_off_t offset, size_t size)
      : ddk::MmioView(mmio, offset, size) {}

  void ModifyBit(size_t idx, bool val) const {
    ddk::MmioView::ModifyBit<uint16_t>(val, idx % 16, Idx2Offset(idx));
  }

  uint16_t GetBit(size_t idx) const {
    return ddk::MmioView::GetBit<uint16_t>(idx % 16, Idx2Offset(idx));
  }
};

class GpioDirReg : public GpioBitFieldView {
 public:
  explicit GpioDirReg(mmio_buffer_t& mmio) : GpioBitFieldView(mmio, 0, 0x100) {}
  void SetDir(size_t idx, bool is_out) const { ModifyBit(idx, is_out); }
};

class GpioOutReg : public GpioBitFieldView {
 public:
  explicit GpioOutReg(mmio_buffer_t& mmio) : GpioBitFieldView(mmio, 0x100, 0x100) {}
  void SetVal(size_t idx, bool val) const { ModifyBit(idx, val); }
};

class GpioInReg : public GpioBitFieldView {
 public:
  explicit GpioInReg(mmio_buffer_t& mmio) : GpioBitFieldView(mmio, 0x200, 0x100) {}
  uint16_t GetVal(size_t idx) const { return GetBit(idx); }
};

class GpioPullEnReg : public GpioBitFieldView {
 public:
  explicit GpioPullEnReg(mmio_buffer_t& mmio) : GpioBitFieldView(mmio, 0x500, 0x100) {}

  bool PullEnable(size_t idx) const { return PullEnableInternal(idx, true); }
  bool PullDisable(size_t idx) const { return PullEnableInternal(idx, false); }

 private:
  bool PullEnableInternal(size_t idx, bool val) const {
    if (idx >= sizeof(kGpioPullInGpioRegs) / (sizeof(**kGpioPullInGpioRegs)) ||
        !kGpioPullInGpioRegs[idx / 16][idx % 16]) {
      return false;
    }
    ModifyBit(idx, val);
    return true;
  }
};

class GpioPullSelReg : public GpioBitFieldView {
 public:
  explicit GpioPullSelReg(mmio_buffer_t& mmio) : GpioBitFieldView(mmio, 0x600, 0x100) {}
  bool SetPullUp(size_t idx) const { return SetPullInternal(idx, true); }
  bool SetPullDown(size_t idx) const { return SetPullInternal(idx, false); }

 private:
  bool SetPullInternal(size_t idx, bool up) const {
    if (idx >= sizeof(kGpioPullInGpioRegs) / (sizeof(**kGpioPullInGpioRegs)) ||
        !kGpioPullInGpioRegs[idx / 16][idx % 16]) {
      return false;
    }
    ModifyBit(idx, up);
    return true;
  }
};

class IoConfigReg : public ddk::MmioBuffer {
 public:
  explicit IoConfigReg(mmio_buffer_t& mmio) : ddk::MmioBuffer(mmio) {}
  bool SetPullUp(size_t idx) const { return SetPullInternal(idx, true); }
  bool SetPullDown(size_t idx) const { return SetPullInternal(idx, false); }
  bool PullEnable(size_t idx, PullAmount amount) const { return PullEnableInternal(idx, amount); }
  bool PullDisable(size_t idx) const { return PullEnableInternal(idx, kNoPull); }

 private:
  // This list pull settings not in the GPIO register set, but only here in IOCFG.
  static constexpr struct {
    size_t idx;
    uint32_t reg_offset;
    uint32_t up_down_bit;
    uint32_t pull_bit_start;
    PullAmount pull_amount_sets_reg_to_1;
    PullAmount pull_amount_sets_reg_to_2;
    PullAmount pull_amount_sets_reg_to_3;
  } pull_regs[] = {
      // clang-format off
        {  14, 0x550, 14, 12, kPull10K,  kPull50K,    kPull10K50K},
        {  15, 0x560,  2,  0, kPull10K,  kPull50K,    kPull10K50K},
        {  16, 0x560,  6,  4, kPull10K,  kPull50K,    kPull10K50K},
        {  17, 0x560, 10,  8, kPull10K,  kPull50K,    kPull10K50K},

        {  21, 0x560, 14, 12, kPull10K,  kPull50K,    kPull10K50K},
        {  22, 0x570,  2,  0, kPull10K,  kPull50K,    kPull10K50K},
        {  23, 0x570,  6,  4, kPull10K,  kPull50K,    kPull10K50K},

        {  40, 0x580,  2,  0, kPull75K,   kPull2K,     kPull75K2K},
        {  41, 0x580,  6,  4, kPull75K,   kPull2K,     kPull75K2K},
        {  42, 0x590,  2,  0, kPull75K, kPull200K,   kPull75K200K},
        {  43, 0x590,  6,  4, kPull75K, kPull200K,   kPull75K200K},

        {  68, 0x550, 10,  8, kPull10K,  kPull50K,    kPull10K50K},
        {  69, 0x550,  6,  4, kPull10K,  kPull50K,    kPull10K50K},
        {  70, 0x540,  6,  4, kPull10K,  kPull50K,    kPull10K50K},
        {  71, 0x540, 10,  8, kPull10K,  kPull50K,    kPull10K50K},
        {  72, 0x540, 14, 12, kPull10K,  kPull50K,    kPull10K50K},
        {  73, 0x550,  2,  0, kPull10K,  kPull50K,    kPull10K50K},

        { 104, 0x540,  2,  0, kPull10K,  kPull50K,    kPull10K50K},
        { 105, 0x530, 14, 12, kPull10K,  kPull50K,    kPull10K50K},
        { 106, 0x520, 14, 12, kPull10K,  kPull50K,    kPull10K50K},
        { 107, 0x530,  2,  0, kPull10K,  kPull50K,    kPull10K50K},
        { 108, 0x530,  6,  4, kPull10K,  kPull50K,    kPull10K50K},
        { 109, 0x530, 10,  8, kPull10K,  kPull50K,    kPull10K50K},
        { 110, 0x510, 14, 12, kPull10K,  kPull50K,    kPull10K50K},
        { 111, 0x510, 10,  8, kPull10K,  kPull50K,    kPull10K50K},
        { 112, 0x510,  6,  4, kPull10K,  kPull50K,    kPull10K50K},
        { 113, 0x510,  2,  0, kPull10K,  kPull50K,    kPull10K50K},
        { 114, 0x520, 10,  8, kPull10K,  kPull50K,    kPull10K50K},
        { 115, 0x520,  2,  0, kPull10K,  kPull50K,    kPull10K50K},
        { 116, 0x520,  6,  4, kPull10K,  kPull50K,    kPull10K50K},
        { 117, 0x500, 14, 12, kPull10K,  kPull50K,    kPull10K50K},
        { 118, 0x500, 10,  8, kPull10K,  kPull50K,    kPull10K50K},
        { 119, 0x500,  6,  4, kPull10K,  kPull50K,    kPull10K50K},
        { 120, 0x500,  2,  0, kPull10K,  kPull50K,    kPull10K50K},
      // clang-format on
  };

  bool SetPullInternal(size_t idx, bool up) const {
    for (auto& i : pull_regs) {
      if (i.idx == idx) {
        ModifyBit<uint16_t>(!up, i.up_down_bit, i.reg_offset);
        return true;
      }
    }
    return false;
  }
  bool PullEnableInternal(size_t idx, PullAmount pull) const {
    for (auto& i : pull_regs) {
      if (i.idx == idx) {
        uint16_t val;
        if (pull == kNoPull) {
          val = 0;
        } else if (pull == i.pull_amount_sets_reg_to_1) {
          val = 1;
        } else if (pull == i.pull_amount_sets_reg_to_2) {
          val = 2;
        } else if (pull == i.pull_amount_sets_reg_to_3) {
          val = 3;
        } else {
          return false;  // Not supported pull amount for this GPIO.
        }
        ModifyBits<uint16_t>(val, i.pull_bit_start, 2, i.reg_offset);
        return true;
      }
    }
    return false;
  }
};

class ExtendedInterruptReg : public ddk::MmioBuffer {
 public:
  explicit ExtendedInterruptReg(mmio_buffer_t& mmio) : ddk::MmioBuffer(mmio) {}
  void Enable(size_t idx) const { EnableInternal(idx, true); }
  void Disable(size_t idx) const { EnableInternal(idx, false); }
  bool IsEnabled(size_t idx) const {
    return !GetBit<uint32_t>(idx % kBitsPerReg, 0x80 + (idx / kBitsPerReg) * kBytesRegSeparation);
  }
  void SetPolarity(size_t idx, bool high) const {
    SetBit<uint32_t>(idx % kBitsPerReg,
                     (high ? 0x340 : 0x380) + (idx / kBitsPerReg) * kBytesRegSeparation);
  }
  void SetEdge(size_t idx, bool edge) const {
    SetBit<uint32_t>(idx % kBitsPerReg,
                     (edge ? 0x1C0 : 0x180) + (idx / kBitsPerReg) * kBytesRegSeparation);
  }

  void SetDomain0(size_t idx) const {
    // These registers are not described on the reference manual.
    SetBit<uint32_t>(idx % kBitsPerReg, 0x400 + (idx / kBitsPerReg) * kBytesRegSeparation);
  }
  void AckInterrupt(size_t idx) const {
    // These registers are not described on the reference manual.
    SetBit<uint32_t>(idx % kBitsPerReg, 0x040 + (idx / kBitsPerReg) * kBytesRegSeparation);
  }
  uint32_t GetNextInterrupt(uint32_t start) const {
    for (uint32_t i = start; i < MT8167_GPIO_EINT_MAX; i += kBitsPerReg) {
      // These registers are not described on the reference manual.
      auto reg = Read<uint32_t>(0x000 + (i / kBitsPerReg) * kBytesRegSeparation);
      if (reg) {
        return i + 31 - __builtin_clz(reg);
      }
    }
    return kInvalidInterruptIdx;
  }

  static constexpr uint32_t kInvalidInterruptIdx = static_cast<uint32_t>(-1);

 private:
  void EnableInternal(size_t idx, bool enable) const {
    SetBit<uint32_t>(idx % kBitsPerReg,
                     (enable ? 0x100 : 0xC0) + (idx / kBitsPerReg) * kBytesRegSeparation);
  }
  static constexpr uint32_t kBitsPerReg = 32;
  static constexpr uint32_t kBytesRegSeparation = 4;
};
}  // namespace gpio
